﻿using System;
using System.Configuration;
using System.Configuration.Provider;
using System.Linq;
using System.Net.Mail;
using System.Web.Configuration;
using Altairis.MailToolkit.Configuration;

namespace Altairis.MailToolkit {

    public static class MailingListManager {
        private static MailToolkitConfigurationSection config;

        // Provider infrastructure

        static MailingListManager() {
            // Get configuration info
            config = ConfigurationManager.GetSection("altairis.mailToolkit") as MailToolkitConfigurationSection;
            if (config == null || config.ListManager.Lists == null || config.ListManager.Lists.Count == 0) throw new ProviderException("No mailing list providers specified");

            // Read verification key
            try {
                VerificationKey = System.Convert.FromBase64String(config.ListManager.VerificationKey);
            }
            catch (Exception ex) {
                throw new ProviderException("Invalid verification key.", ex);
            }

            // Instantiate the providers
            Lists = new MailingListProviderCollection();
            ProvidersHelper.InstantiateProviders(config.ListManager.Lists, Lists, typeof(MailingListProvider));
            Lists.SetReadOnly();
        }

        // Configuration properties

        public static byte[] VerificationKey { get; private set; }

        public static MailingListProviderCollection Lists { get; private set; }

        // Manager methods

        /// <summary>
        /// Sends messages to the mailing list.
        /// </summary>
        /// <param name="listName">Name of the mailing list.</param>
        /// <param name="subject">The message subject.</param>
        /// <param name="body">The message body.</param>
        /// <param name="additionalArgs">The additional args.</param>
        /// <exception cref="System.ArgumentNullException">
        /// listName
        /// or
        /// subject
        /// or
        /// body
        /// </exception>
        /// <exception cref="System.ArgumentException">
        /// Value cannot be null or whitespace-only string.;listName
        /// or
        /// Value cannot be null or whitespace-only string.;subject
        /// or
        /// Value cannot be null or whitespace-only string.;body
        /// </exception>
        /// <exception cref="System.ArgumentOutOfRangeException">listName;List not found.</exception>
        public static void SendMessages(string listName, string subject, string body, params object[] additionalArgs) {
            // Validate arguments
            if (listName == null) throw new ArgumentNullException("listName");
            if (string.IsNullOrWhiteSpace(listName)) throw new ArgumentException("Value cannot be null or whitespace-only string.", "listName");
            if (subject == null) throw new ArgumentNullException("subject");
            if (string.IsNullOrWhiteSpace(subject)) throw new ArgumentException("Value cannot be null or whitespace-only string.", "subject");
            if (body == null) throw new ArgumentNullException("body");
            if (string.IsNullOrWhiteSpace(body)) throw new ArgumentException("Value cannot be null or whitespace-only string.", "body");

            // Get list provider
            var list = Lists[listName];
            if (list == null) throw new ArgumentOutOfRangeException("listName", "List not found.");

            // Get members
            var listMembers = list.GetMembers().Select(x => new MailAddress(x));

            // Add formatting arguments
            object[] args;
            if (additionalArgs == null || additionalArgs.Length == 0) {
                args = new object[4];
            }
            else {
                args = new object[additionalArgs.Length + 4];
                Array.Copy(additionalArgs, 0, args, 4, additionalArgs.Length);
            }
            args[0] = subject;          // 0
            args[1] = body;             // 1
            args[2] = list.Name;        // 2
            args[3] = list.Description; // 3

            // Send messages
            Mailer.SendTemplatedMessages(
                config.ListManager.TransportProviderName,   // transport provider
                null,                                       // culture
                null,                                       // reply-to
                listMembers,                                // recipients
                list.MessageTemplate,                       // template name
                args);                                      // formatting arguments
        }

        // Provider interface

        /// <summary>
        /// Sends message to request confirmation of subscription of given e-mail address to the mailing list.
        /// </summary>
        /// <param name="listName">Name of the list.</param>
        /// <param name="emailAddress">The e-mail address to be subscribed.</param>
        /// <param name="additionalArgs">The additional args.</param>
        /// <returns>
        /// Returns <c>false</c>, when address is already member of the list, <c>true</c> otherwise.
        /// </returns>
        /// <exception cref="System.ArgumentNullException">
        /// listName
        /// or
        /// emailAddress
        /// </exception>
        /// <exception cref="System.ArgumentException">Value cannot be null or whitespace-only string.;listName</exception>
        /// <exception cref="System.ArgumentOutOfRangeException">listName;List not found.</exception>
        public static bool Subscribe(string listName, MailAddress emailAddress, params object[] additionalArgs) {
            // Validate arguments
            if (listName == null) throw new ArgumentNullException("listName");
            if (string.IsNullOrWhiteSpace(listName)) throw new ArgumentException("Value cannot be null or whitespace-only string.", "listName");
            if (emailAddress == null) throw new ArgumentNullException("emailAddress");

            // Get list provider
            var list = Lists[listName];
            if (list == null) throw new ArgumentOutOfRangeException("listName", "List not found.");

            // Call list provider
            return list.Subscribe(emailAddress, additionalArgs);
        }

        /// <summary>
        /// Subscribes the e-mail address to mailing list.
        /// </summary>
        /// <param name="listName">Name of the list.</param>
        /// <param name="emailAddress">The e-mail address.</param>
        /// <param name="code">The confirmation code.</param>
        /// <returns>
        /// Returns <c>true</c> when subscription succeeded or <c>false</c> on invalid confirmation code.
        /// </returns>
        /// <exception cref="System.ArgumentNullException">
        /// listName
        /// or
        /// emailAddress
        /// or
        /// code
        /// </exception>
        /// <exception cref="System.ArgumentException">
        /// Value cannot be null or whitespace-only string.;listName
        /// or
        /// Value cannot be null or whitespace-only string.;code
        /// </exception>
        /// <exception cref="System.ArgumentOutOfRangeException">listName;List not found.</exception>
        public static bool SubscribeConfirm(string listName, MailAddress emailAddress, string code) {
            // Validate arguments
            if (listName == null) throw new ArgumentNullException("listName");
            if (string.IsNullOrWhiteSpace(listName)) throw new ArgumentException("Value cannot be null or whitespace-only string.", "listName");
            if (emailAddress == null) throw new ArgumentNullException("emailAddress");
            if (code == null) throw new ArgumentNullException("code");
            if (string.IsNullOrWhiteSpace(code)) throw new ArgumentException("Value cannot be null or whitespace-only string.", "code");

            // Get list provider
            var list = Lists[listName];
            if (list == null) throw new ArgumentOutOfRangeException("listName", "List not found.");

            // Call list provider
            return list.SubscribeConfirm(emailAddress, code);
        }

        /// <summary>
        /// Subscribes the e-mail address to mailing list, without confirmation.
        /// </summary>
        /// <param name="listName">Name of the list.</param>
        /// <param name="emailAddress">The e-mail address.</param>
        /// <returns>
        /// Returns <c>true</c> when subscription succeeded or <c>false</c> when e-mail is already in list.
        /// </returns>
        /// <exception cref="System.ArgumentNullException">
        /// listName
        /// or
        /// emailAddress
        /// </exception>
        /// <exception cref="System.ArgumentException">Value cannot be null or whitespace-only string.;listName</exception>
        /// <exception cref="System.ArgumentOutOfRangeException">listName;List not found.</exception>
        public static bool SubscribeForce(string listName, MailAddress emailAddress) {
            // Validate arguments
            if (listName == null) throw new ArgumentNullException("listName");
            if (string.IsNullOrWhiteSpace(listName)) throw new ArgumentException("Value cannot be null or whitespace-only string.", "listName");
            if (emailAddress == null) throw new ArgumentNullException("emailAddress");

            // Get list provider
            var list = Lists[listName];
            if (list == null) throw new ArgumentOutOfRangeException("listName", "List not found.");

            // Call list provider
            return list.SubscribeForce(emailAddress);
        }

        /// <summary>
        /// Sends message to request confirmation of removal of given e-mail address from the mailing list.
        /// </summary>
        /// <param name="listName">Name of the list.</param>
        /// <param name="emailAddress">The e-mail address.</param>
        /// <param name="additionalArgs">The additional args.</param>
        /// <returns>
        /// Returns <c>false</c>, when address is not member of the list, <c>true</c> otherwise.
        /// </returns>
        /// <exception cref="System.ArgumentNullException">
        /// listName
        /// or
        /// emailAddress
        /// </exception>
        /// <exception cref="System.ArgumentException">Value cannot be null or whitespace-only string.;listName</exception>
        /// <exception cref="System.ArgumentOutOfRangeException">listName;List not found.</exception>
        public static bool Remove(string listName, MailAddress emailAddress, params object[] additionalArgs) {
            // Validate arguments
            if (listName == null) throw new ArgumentNullException("listName");
            if (string.IsNullOrWhiteSpace(listName)) throw new ArgumentException("Value cannot be null or whitespace-only string.", "listName");
            if (emailAddress == null) throw new ArgumentNullException("emailAddress");

            // Get list provider
            var list = Lists[listName];
            if (list == null) throw new ArgumentOutOfRangeException("listName", "List not found.");

            // Call list provider
            return list.Remove(emailAddress, additionalArgs);
        }

        /// <summary>
        /// Removes the e-mail address from mailing list.
        /// </summary>
        /// <param name="listName">Name of the list.</param>
        /// <param name="emailAddress">The e-mail address.</param>
        /// <param name="code">The confirmation code.</param>
        /// <returns>
        /// Returns <c>true</c> when removal succeeded or <c>false</c> on invalid confirmation code.
        /// </returns>
        /// <exception cref="System.ArgumentNullException">
        /// listName
        /// or
        /// emailAddress
        /// or
        /// code
        /// </exception>
        /// <exception cref="System.ArgumentException">
        /// Value cannot be null or whitespace-only string.;listName
        /// or
        /// Value cannot be null or whitespace-only string.;code
        /// </exception>
        /// <exception cref="System.ArgumentOutOfRangeException">listName;List not found.</exception>
        public static bool RemoveConfirm(string listName, MailAddress emailAddress, string code) {
            // Validate arguments
            if (listName == null) throw new ArgumentNullException("listName");
            if (string.IsNullOrWhiteSpace(listName)) throw new ArgumentException("Value cannot be null or whitespace-only string.", "listName");
            if (emailAddress == null) throw new ArgumentNullException("emailAddress");
            if (code == null) throw new ArgumentNullException("code");
            if (string.IsNullOrWhiteSpace(code)) throw new ArgumentException("Value cannot be null or whitespace-only string.", "code");

            // Get list provider
            var list = Lists[listName];
            if (list == null) throw new ArgumentOutOfRangeException("listName", "List not found.");

            // Call list provider
            return list.RemoveConfirm(emailAddress, code);
        }

        /// <summary>
        /// Removes the e-mail address from mailing list, without confirmation.
        /// </summary>
        /// <param name="listName">Name of the list.</param>
        /// <param name="emailAddress">The e-mail address.</param>
        /// <returns>
        /// Returns <c>true</c> when removal succeeded or <c>false</c> when e-mail is not in list.
        /// </returns>
        /// <exception cref="System.ArgumentNullException">
        /// listName
        /// or
        /// emailAddress
        /// </exception>
        /// <exception cref="System.ArgumentException">Value cannot be null or whitespace-only string.;listName</exception>
        /// <exception cref="System.ArgumentOutOfRangeException">listName;List not found.</exception>
        public static bool RemoveForce(string listName, MailAddress emailAddress) {
            // Validate arguments
            if (listName == null) throw new ArgumentNullException("listName");
            if (string.IsNullOrWhiteSpace(listName)) throw new ArgumentException("Value cannot be null or whitespace-only string.", "listName");
            if (emailAddress == null) throw new ArgumentNullException("emailAddress");

            // Get list provider
            var list = Lists[listName];
            if (list == null) throw new ArgumentOutOfRangeException("listName", "List not found.");

            // Call list provider
            return list.RemoveForce(emailAddress);
        }

        /// <summary>
        /// Gets the number of list members.
        /// </summary>
        /// <param name="listName">Name of the list.</param>
        /// <returns>
        /// Number of list members.
        /// </returns>
        /// <exception cref="System.ArgumentNullException">listName</exception>
        /// <exception cref="System.ArgumentException">Value cannot be null or whitespace-only string.;listName</exception>
        /// <exception cref="System.ArgumentOutOfRangeException">listName;List not found.</exception>
        public static int GetMemberCount(string listName) {
            // Validate arguments
            if (listName == null) throw new ArgumentNullException("listName");
            if (string.IsNullOrWhiteSpace(listName)) throw new ArgumentException("Value cannot be null or whitespace-only string.", "listName");

            // Get list provider
            var list = Lists[listName];
            if (list == null) throw new ArgumentOutOfRangeException("listName", "List not found.");

            // Call list provider
            return list.GetMemberCount();
        }
    }
}